package com.yckj.ytracker.plugin.mybatis;

import com.yckj.ytracker.api.config.Config;
import com.yckj.ytracker.api.enums.TrackerStatus;
import com.yckj.ytracker.api.enums.TrackerType;
import com.yckj.ytracker.api.factory.TrackerFactory;
import com.yckj.ytracker.api.trace.ITracerClient;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.*;

/**
 * @author liuwei
 * @version 2017/10/18 下午11:59
 * @description 调用链mybatis拦截器
 * @see
 * @since 1.0
 */
public class MapperInterceptor implements Interceptor {
    private static final Logger LOG = LoggerFactory.getLogger(MapperInterceptor.class);

    private static ITracerClient tracerClient;
    private static Properties properties;

    //默认开启
    private static boolean isClose=false;
    //集群名
    private  static String clusterName;

    static {
        tracerClient = TrackerFactory.getTracker(TrackerType.MYBATIS);
        String configUrl = MapperInterceptor.class.getResource("/").getPath();
        properties = new Properties();
        String config = configUrl + "ytracker.properties";
        FileInputStream fis = null;
        try {
            try {
                fis = new FileInputStream(config);
                properties.load(fis);
            } catch (Exception e) {
                LOG.info("{} ytracker.properties not found!", configUrl);
            }
            Config.init(properties);
            clusterName = properties.getProperty("clusterName") == null ? "mybatis" : properties.getProperty("clusterName");
        } catch (Exception e) {
            LOG.error("MapperInterceptor init error:{}", e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    LOG.error("MapperInterceptor init error:{}", e);
                }
            }
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();

        MappedStatement mappedStatement = null;
        BoundSql boundSql = null;
        Configuration configuration = null;
        Object parameter = null;
        String sqlStr = null;
        String pstr = null;
        StringBuilder metaString = new StringBuilder();
        Object returnValue = null;
        long start = System.currentTimeMillis();
        long startParam = System.currentTimeMillis();
        Map<String, String> map = new HashMap<String, String>();
        try {
            mappedStatement = (MappedStatement) args[0];
            if (args.length > 1) {
                parameter = invocation.getArgs()[1];
            }
            //获取sql语句
            boundSql = mappedStatement.getBoundSql(parameter);
            sqlStr = boundSql.getSql().replaceAll("[\\s]+", " ");
            //获取初始化信息
            configuration = mappedStatement.getConfiguration();
            //获取参数信息
            pstr = getPrameters(configuration, mappedStatement.getBoundSql(parameter));
            //计时
            long endParam = System.currentTimeMillis() - startParam;
            map.put("paramConsume", endParam + "");
            //调用链传递数据
            tracerClient.startSpan(sqlStr, clusterName, metaString.append(pstr).toString());
            start = System.currentTimeMillis();
        } catch (Exception e) {
            tracerClient.offSpan(TrackerStatus.ERROR, e);
            e.printStackTrace();
        }
        returnValue = invocation.proceed();
        try {
            long end = System.currentTimeMillis() - start;
            map.put("dbConsume", end + "");
            tracerClient.setAttributes(map);
            if (!isClose) {
                tracerClient.offSpan(TrackerStatus.SUCCESS);
            }
        } catch (Exception e) {
            tracerClient.offSpan(TrackerStatus.ERROR, e);
            e.printStackTrace();
        }
        return returnValue;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public static String getPrameters(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        StringBuilder paramStr = new StringBuilder();
        if (parameterObject != null && !parameterMappings.isEmpty()) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                paramStr.append(getParameterValue(parameterObject)).append(";");
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        paramStr.append(getParameterValue(obj)).append(";");
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        paramStr.append(getParameterValue(obj)).append(";");
                    }
                }
            }
        }
        return paramStr.toString();
    }

    private static String getParameterValue(Object obj) {
        StringBuilder value = new StringBuilder("");
        if (obj instanceof String) {
            value.append("'" + obj.toString() + "'");
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value.append("'" + formatter.format((Date) obj) + "'");
        } else {
            if (obj != null) {
                value.append(obj.toString());
            }
        }
        return value.toString();
    }
}
